package dk.mirasola.systemtraining.server;

import dk.mirasola.systemtraining.bridgewidgets.shared.factory.SeatFactory;
import dk.mirasola.systemtraining.bridgewidgets.shared.factory.ShapeFactory;
import dk.mirasola.systemtraining.bridgewidgets.shared.factory.ShapeTypeFactory;
import dk.mirasola.systemtraining.bridgewidgets.shared.factory.SuitFactory;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.Seat;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.Suit;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.CardsCountInterval;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.CompareOperator;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.DistributionFilterGroup;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.DistributionFilterGroupItem;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.GroupItem;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.HcpInterval;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.HcpSeatDistributionFilter;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.HcpSuitDistributionFilter;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.LogicOperator;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.LogicOperatorGroupItem;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.MajorMinorDistributionFilter;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.NTDistributionFilter;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.ShapeDistributionFilter;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.ShapeTypeDistributionFilter;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.FitDistributionFilter;

import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

public class DistributionFilterGroupFactory {
    private static final Pattern spaceSplitter = Pattern.compile(" ");
    private static final Pattern semicolonSplitter = Pattern.compile(";");

    public static String distributionFilterGroupToString(DistributionFilterGroup distributionFilterGroup) {
        if (distributionFilterGroup == null) {
            throw new NullPointerException();
        }

        StringBuilder sb = new StringBuilder();
        distributionFilterGroupToString(distributionFilterGroup, sb);
        return sb.toString();
    }

    public static DistributionFilterGroup stringToDistributionFilterGroup(String str) {
        DistributionFilterGroup top = null;
        DistributionFilterGroup current = null;

        String[] tokens = spaceSplitter.split(str);
        if (!tokens[0].endsWith("(")) {
            throw new RuntimeException("cannot parse: " + str);
        }
        for (String token : tokens) {
            if (token.endsWith("(")) {
                // DistributionFilter group
                boolean inverted = token.startsWith("!");
                String nameUnescaped = null;
                if (inverted) {
                    nameUnescaped = token.substring(1, token.length() - 1);
                } else {
                    nameUnescaped = token.substring(0, token.length() - 1);
                }
                String groupName = nameUnescaped.replace("¤¤¤", " ");
                DistributionFilterGroup group = new DistributionFilterGroup(groupName);
                group.setInverted(inverted);

                if (top == null) {
                    top = group;
                    current = group;
                } else {
                    current.getGroupItems().add(group);
                    current = group;
                }
            } else if (token.equals(")")) {
                current = current.getGroup();
            } else if (token.equals("&")) {
                LogicOperatorGroupItem item = new LogicOperatorGroupItem(LogicOperator.AND);
                current.getGroupItems().add(item);
            } else if (token.equals("|")) {
                LogicOperatorGroupItem item = new LogicOperatorGroupItem(LogicOperator.OR);
                current.getGroupItems().add(item);
            } else {
                current.getGroupItems().add(stringToLeafDistributionFilterGroupItem(token));
            }
        }

        return top;
    }

    private static DistributionFilterGroupItem stringToLeafDistributionFilterGroupItem(String str) {
        boolean inverted = str.startsWith("!");
        String[] leafProps = null;
        if (inverted) {
            leafProps = semicolonSplitter.split(str.substring(1));
        } else {
            leafProps = semicolonSplitter.split(str);
        }
        DistributionFilterGroupItem leaf = null;
        if ("SH".equals(leafProps[0])) {
            leaf = toShapeDistributionFilter(leafProps);
        } else if ("ST".equals(leafProps[0])) {
            leaf = toShapeTypeDistributionFilter(leafProps);
        } else if ("FT".equals(leafProps[0])) {
            leaf = toFitDistributionFilter(leafProps);
        } else if ("H1".equals(leafProps[0])) {
            leaf = toHcpSuitDistributionFilter(leafProps);
        } else if ("H2".equals(leafProps[0])) {
            leaf = toHcpSeatDistributionFilter(leafProps);
        } else if ("MM".equals(leafProps[0])) {
            leaf = toMajorMinorDistributionFilter(leafProps);
        } else if ("NT".equals(leafProps[0])) {
            leaf = toNTDistributionFilter(leafProps);
        } else {
            throw new RuntimeException("Cannot parse: " + str);
        }
        leaf.setInverted(inverted);
        return leaf;
    }

    private static NTDistributionFilter toNTDistributionFilter(String[] props) {
        NTDistributionFilter ntDistributionFilter = new NTDistributionFilter();
        ntDistributionFilter.setSeat(SeatFactory.stringToSeat(props[1]));
        ntDistributionFilter.setHcpInterval(stringToHcpInterval(props[2]));
        ntDistributionFilter.setFiveCardMajorsAllowed(stringToBoolean(props[3]));
        ntDistributionFilter.setSixCardMinorsAllowed(stringToBoolean(props[4]));
        return ntDistributionFilter;
    }

    private static MajorMinorDistributionFilter toMajorMinorDistributionFilter(String[] props) {
        MajorMinorDistributionFilter majorMinorDistributionFilter = new MajorMinorDistributionFilter();
        majorMinorDistributionFilter.setSeat(SeatFactory.stringToSeat(props[1]));
        majorMinorDistributionFilter.setMajorCardsCountInterval(stringToCardsCountInterval(props[2]));
        majorMinorDistributionFilter.setMinorCardsCountInterval(stringToCardsCountInterval(props[3]));
        return majorMinorDistributionFilter;
    }

    private static DistributionFilterGroupItem toHcpSeatDistributionFilter(String[] props) {
        HcpSeatDistributionFilter hcpSeatDistributionFilter = new HcpSeatDistributionFilter();
        hcpSeatDistributionFilter.setSeats(stringToSeats(props[1]));
        hcpSeatDistributionFilter.setHcpInterval(stringToHcpInterval(props[2]));
        return hcpSeatDistributionFilter;
    }

    private static HcpSuitDistributionFilter toHcpSuitDistributionFilter(String[] props) {
        HcpSuitDistributionFilter hcpSuitDistributionFilter = new HcpSuitDistributionFilter();
        hcpSuitDistributionFilter.setSeat(SeatFactory.stringToSeat(props[1]));
        hcpSuitDistributionFilter.setSuits(stringToSuits(props[2]));
        hcpSuitDistributionFilter.setHcpInterval(stringToHcpInterval(props[3]));
        return hcpSuitDistributionFilter;
    }

    private static FitDistributionFilter toFitDistributionFilter(String[] props) {
        FitDistributionFilter fitDistributionFilter = new FitDistributionFilter();
        fitDistributionFilter.setSeats(stringToSeats(props[1]));
        fitDistributionFilter.setSuits(stringToSuits(props[2]));
        fitDistributionFilter.setComparator(CompareOperator.valueOfSymbol(props[3]));
        fitDistributionFilter.setTotalCards(Integer.valueOf(props[4]));
        return fitDistributionFilter;
    }

    private static ShapeTypeDistributionFilter toShapeTypeDistributionFilter(String[] props) {
        ShapeTypeDistributionFilter shapeTypeDistributionFilter = new ShapeTypeDistributionFilter();
        shapeTypeDistributionFilter.setSeat(SeatFactory.stringToSeat(props[1]));
        shapeTypeDistributionFilter.setShapeType(ShapeTypeFactory.stringToShapeType(props[2]));
        return shapeTypeDistributionFilter;
    }

    private static ShapeDistributionFilter toShapeDistributionFilter(String[] props) {
        ShapeDistributionFilter shapeDistributionFilter = new ShapeDistributionFilter();
        shapeDistributionFilter.setSeat(SeatFactory.stringToSeat(props[1]));
        shapeDistributionFilter.setShape(ShapeFactory.stringToShape(props[2]));
        return shapeDistributionFilter;
    }


    private static void distributionFilterGroupToString(DistributionFilterGroup distributionFilterGroup, StringBuilder sb) {
        if (distributionFilterGroup.getInverted()) {
            sb.append("!");
        }
        sb.append(distributionFilterGroup.getName().replace(" ", "¤¤¤"));
        sb.append("(");
        for (GroupItem groupItem : distributionFilterGroup.getGroupItems()) {
            sb.append(" ");
            groupItemToString(groupItem, sb);
        }
        sb.append(" )");
    }


    private static void groupItemToString(GroupItem groupItem, StringBuilder sb) {
        if (groupItem instanceof DistributionFilterGroup) {
            distributionFilterGroupToString((DistributionFilterGroup) groupItem, sb);
        } else if (groupItem instanceof HcpSeatDistributionFilter) {
            hcpSeatDistributionFilterToString((HcpSeatDistributionFilter) groupItem, sb);
        } else if (groupItem instanceof HcpSuitDistributionFilter) {
            hcpSuitDistributionFilterToString((HcpSuitDistributionFilter) groupItem, sb);
        } else if (groupItem instanceof NTDistributionFilter) {
            ntDistributionFilterToString((NTDistributionFilter) groupItem, sb);
        } else if (groupItem instanceof MajorMinorDistributionFilter) {
            majorMinorDistributionFilterToString((MajorMinorDistributionFilter) groupItem, sb);
        } else if (groupItem instanceof FitDistributionFilter) {
            fitDistributionFilterToString((FitDistributionFilter) groupItem, sb);
        } else if (groupItem instanceof ShapeDistributionFilter) {
            shapeDistributionFilterToString((ShapeDistributionFilter) groupItem, sb);
        } else if (groupItem instanceof ShapeTypeDistributionFilter) {
            shapeTypeDistributionFilterToString((ShapeTypeDistributionFilter) groupItem, sb);
        } else if (groupItem instanceof LogicOperatorGroupItem) {
            logicOperatorGroupItemToString((LogicOperatorGroupItem) groupItem, sb);
        } else {
            throw new RuntimeException("unknown group item: " + groupItem);
        }
    }

    private static void logicOperatorGroupItemToString(LogicOperatorGroupItem logicOperatorGroupItem, StringBuilder sb) {
        sb.append(logicOperatorGroupItem.getLogicOperator() == LogicOperator.AND ? "&" : "|");
    }

    private static void shapeTypeDistributionFilterToString(ShapeTypeDistributionFilter shapeTypeDistributionFilter, StringBuilder sb) {
        if (shapeTypeDistributionFilter.getInverted()) {
            sb.append("!");
        }
        sb.append("ST;");
        sb.append(SeatFactory.seatToString(shapeTypeDistributionFilter.getSeat()));
        sb.append(";");
        ShapeTypeFactory.shapeTypeToString(shapeTypeDistributionFilter.getShapeType(), sb);
    }

    private static void shapeDistributionFilterToString(ShapeDistributionFilter shapeDistributionFilter, StringBuilder sb) {
        if (shapeDistributionFilter.getInverted()) {
            sb.append("!");
        }
        sb.append("SH;");
        sb.append(SeatFactory.seatToString(shapeDistributionFilter.getSeat()));
        sb.append(";");
        ShapeFactory.shapeToString(shapeDistributionFilter.getShape(), sb);
    }

    private static void fitDistributionFilterToString(FitDistributionFilter fitDistributionFilter, StringBuilder sb) {
        if (fitDistributionFilter.getInverted()) {
            sb.append("!");
        }
        sb.append("FT;");
        seatsToString(fitDistributionFilter.getSeats(), sb);
        sb.append(";");
        suitsToString(fitDistributionFilter.getSuits(), sb);
        sb.append(";");
        sb.append(fitDistributionFilter.getComparator().getSymbol());
        sb.append(";");
        sb.append(fitDistributionFilter.getTotalCards());
    }

    private static void majorMinorDistributionFilterToString(MajorMinorDistributionFilter majorMinorDistributionFilter, StringBuilder sb) {
        if (majorMinorDistributionFilter.getInverted()) {
            sb.append("!");
        }
        sb.append("MM;");
        sb.append(SeatFactory.seatToString(majorMinorDistributionFilter.getSeat()));
        sb.append(";");
        cardsCountIntervalToString(majorMinorDistributionFilter.getMajorCardsCountInterval(), sb);
        sb.append(";");
        cardsCountIntervalToString(majorMinorDistributionFilter.getMinorCardsCountInterval(), sb);
    }

    private static void ntDistributionFilterToString(NTDistributionFilter ntDistributionFilter, StringBuilder sb) {
        if (ntDistributionFilter.getInverted()) {
            sb.append("!");
        }
        sb.append("NT;");
        sb.append(SeatFactory.seatToString(ntDistributionFilter.getSeat()));
        sb.append(";");
        hcpIntervalToString(ntDistributionFilter.getHcpInterval(), sb);
        sb.append(";");
        booleanToString(ntDistributionFilter.getFiveCardMajorsAllowed(), sb);
        sb.append(";");
        booleanToString(ntDistributionFilter.getSixCardMinorsAllowed(), sb);
    }

    private static void hcpSuitDistributionFilterToString(HcpSuitDistributionFilter hcpSuitDistributionFilter, StringBuilder sb) {
        if (hcpSuitDistributionFilter.getInverted()) {
            sb.append("!");
        }
        sb.append("H1;");
        sb.append(SeatFactory.seatToString(hcpSuitDistributionFilter.getSeat()));
        sb.append(";");
        suitsToString(hcpSuitDistributionFilter.getSuits(), sb);
        sb.append(";");
        hcpIntervalToString(hcpSuitDistributionFilter.getHcpInterval(), sb);
    }

    private static void hcpSeatDistributionFilterToString(HcpSeatDistributionFilter hcpSeatDistributionFilter, StringBuilder sb) {
        if (hcpSeatDistributionFilter.getInverted()) {
            sb.append("!");
        }
        sb.append("H2;");
        seatsToString(hcpSeatDistributionFilter.getSeats(), sb);
        sb.append(";");
        hcpIntervalToString(hcpSeatDistributionFilter.getHcpInterval(), sb);
    }

    public static void hcpIntervalToString(HcpInterval hcpInterval, StringBuilder sb) {
        sb.append(hcpInterval.getMinHcp());
        sb.append("-");
        sb.append(hcpInterval.getMaxHcp());
    }

    public static void cardsCountIntervalToString(CardsCountInterval interval, StringBuilder sb) {
        sb.append(interval.getMinCardsCount());
        sb.append("-");
        sb.append(interval.getMaxCardsCount());
    }

    public static HcpInterval stringToHcpInterval(String str) {
        int idx = str.indexOf("-");
        if (idx > 0) {
            try {
                int min = Integer.valueOf(str.substring(0, idx));
                int max = Integer.valueOf(str.substring(idx + 1));
                return HcpInterval.create(min, max, false);
            } catch (NumberFormatException e) {
                throw new RuntimeException("Cannot parse '" + str + "' to " + HcpInterval.class.getSimpleName());
            }
        } else {
            throw new RuntimeException("Cannot parse '" + str + "' to " + HcpInterval.class.getSimpleName());
        }
    }

    public static CardsCountInterval stringToCardsCountInterval(String str) {
        int idx = str.indexOf("-");
        if (idx > 0) {
            try {
                int min = Integer.valueOf(str.substring(0, idx));
                int max = Integer.valueOf(str.substring(idx + 1));
                return CardsCountInterval.create(min, max, false);
            } catch (NumberFormatException e) {
                throw new RuntimeException("Cannot parse '" + str + "' to " + CardsCountInterval.class.getSimpleName());
            }
        } else {
            throw new RuntimeException("Cannot parse '" + str + "' to " + CardsCountInterval.class.getSimpleName());
        }
    }

    private static void booleanToString(boolean b, StringBuilder sb) {
        sb.append(b ? "t" : "f");
    }

    private static Boolean stringToBoolean(String str) {
        return "t".equals(str);
    }

    private static void seatsToString(Set<Seat> seats, StringBuilder sb) {
        for (Seat seat : seats) {
            sb.append(SeatFactory.seatToString(seat));
        }
    }

    private static void suitsToString(Set<Suit> suits, StringBuilder sb) {
        for (Suit suit : suits) {
            sb.append(SuitFactory.suitToString(suit));
        }
    }

    private static Set<Seat> stringToSeats(String seatsStr) {
        Set<Seat> res = new HashSet<Seat>();
        for (int i = 0; i < seatsStr.length(); i++) {
            res.add(SeatFactory.charToSeat(seatsStr.charAt(i)));
        }
        return res;
    }

    private static Set<Suit> stringToSuits(String suitsStr) {
        Set<Suit> res = new HashSet<Suit>();
        for (int i = 0; i < suitsStr.length(); i++) {
            res.add(SuitFactory.charToSeat(suitsStr.charAt(i)));
        }
        return res;
    }


    private DistributionFilterGroupFactory() {
    }
}
